﻿/* 
   Copyright 2011 LUCA Studios LLC & John Evans

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

namespace LUCAStudios.Silverlight.Controls.Windows
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    using DragDrop;

    /// <summary>
    /// A base class which provides dragging and resizing transformations for window-type elements.
    /// </summary>
    public abstract class WindowManipulatorBase : DragDropManager
    {
        #region fields

        #endregion

        #region events

        /// <summary>
        /// Generalized event firing wrapper since all the events use the same event handler type
        /// </summary>
        /// <param name="handler">delegate name of the event to call</param>
        /// <param name="win">IWindow reference (on which the event is occurring) to pass into LUCAWindowEventArgs</param>
        private void OnWindowEvent(EventHandler<WindowManipulationEventArgs> handler, IWindow win)
        {
            if (handler != null) handler(this, new WindowManipulationEventArgs(win));
        }

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="rootPanel"></param>
        protected WindowManipulatorBase(Panel rootPanel)
            : base(rootPanel)
        {
            DragStarted += (sender, args) => OnWindowEvent(WindowDragStart, args.Source as IWindow);
            DragCompleted += (sender2, args2) => OnWindowEvent(WindowDragComplete, args2.Source as IWindow);
        }

        /// <summary>
        /// Registers a window (contract) to the service.  
        /// </summary>
        /// <param name="window">The window contract to register to the service.</param>
        /// <returns></returns>
        public bool RegisterWindow(IWindow window)
        {

            RegisterDragDropSource(window);
            RegisterDragDropTarget(window);

            /*
             * Register events and lookups for any resize controls found in the IWindow contract.
             * Allows dev to leave some (or all) null if they don't want supported.
             */

            if (window.WidthResizerRight != null)
            {
                window.WidthResizerRight.MouseLeftButtonDown += WidthResizerRightMouseLeftButtonDown;
                ClickTargetLookup.Add(window.WidthResizerRight, window);
            }

            if (window.HeightResizerBottom != null)
            {
                window.HeightResizerBottom.MouseLeftButtonDown += HeightResizerBottomMouseLeftButtonDown;
                ClickTargetLookup.Add(window.HeightResizerBottom, window);
            }

            if (window.WidthResizerLeft != null)
            {
                window.WidthResizerLeft.MouseLeftButtonDown += WidthResizerLeftMouseLeftButtonDown;
                ClickTargetLookup.Add(window.WidthResizerLeft, window);
            }

            if (window.HeightResizerTop != null)
            {
                window.HeightResizerTop.MouseLeftButtonDown += HeightResizerTopMouseLeftButtonDown;
                ClickTargetLookup.Add(window.HeightResizerTop, window);
            }

            if (window.ResizerTopLeft != null)
            {
                window.ResizerTopLeft.MouseLeftButtonDown += ResizerTopLeftMouseLeftButtonDown;
                ClickTargetLookup.Add(window.ResizerTopLeft, window);
            }

            if (window.ResizerTopRight != null)
            {
                window.ResizerTopRight.MouseLeftButtonDown += ResizerTopRightMouseLeftButtonDown;
                ClickTargetLookup.Add(window.ResizerTopRight, window);
            }

            if (window.ResizerBottomLeft != null)
            {
                window.ResizerBottomLeft.MouseLeftButtonDown += ResizerBottomLeftMouseLeftButtonDown;
                ClickTargetLookup.Add(window.ResizerBottomLeft, window);
            }

            if (window.ResizerBottomRight != null)
            {
                window.ResizerBottomRight.MouseLeftButtonDown += ResizerBottomRightMouseLeftButtonDown;
                ClickTargetLookup.Add(window.ResizerBottomRight, window);
            }

            return true;
        }
        /// <summary>
        /// Registers a window (IWindow) to the service.  Also sets initial visual offset for the window visual element
        /// </summary>
        /// <param name="window">IWindow contract</param>
        /// <param name="xOffset">initial x offset (pixels) for the window visual element</param>
        /// <param name="yOffset">initial y offset (pixels) for the window visual element</param>
        /// <returns></returns>
        public bool RegisterWindow(IWindow window, double xOffset, double yOffset)
        {
            if (!RegisterWindow(window)) return false;

            xOffset = xOffset < 0 ? 0 : xOffset;
            yOffset = yOffset < 0 ? 0 : yOffset;

            var tg = window.DragElement.RenderTransform as TransformGroup;
            if (tg == null)
            {
                tg = new TransformGroup();
                tg.Children.Add(window.DragElement.RenderTransform);
            }
            var t = new TranslateTransform { X = xOffset, Y = yOffset };
            tg.Children.Add(t);
            window.DragElement.RenderTransform = tg;
            return true;
        }
        /// <summary>
        /// Removes a previously registered window from the service.
        /// </summary>
        /// <param name="window"></param>
        public void UnregisterWindow(IWindow window)
        {
            UnRegisterDragDropSource(window);
            UnRegisterDragDropTarget(window);

            /*
             * Register events and lookups for any resize controls found in the IWindow contract.
             * Allows dev to leave some (or all) null if they don't want supported.
             */

            if (window.WidthResizerRight != null)
            {
                window.WidthResizerRight.MouseLeftButtonDown -= WidthResizerRightMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.WidthResizerRight);
            }

            if (window.HeightResizerBottom != null)
            {
                window.HeightResizerBottom.MouseLeftButtonDown -= HeightResizerBottomMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.HeightResizerBottom);
            }

            if (window.WidthResizerLeft != null)
            {
                window.WidthResizerLeft.MouseLeftButtonDown -= WidthResizerLeftMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.WidthResizerLeft);
            }

            if (window.HeightResizerTop != null)
            {
                window.HeightResizerTop.MouseLeftButtonDown -= HeightResizerTopMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.HeightResizerTop);
            }

            if (window.ResizerTopLeft != null)
            {
                window.ResizerTopLeft.MouseLeftButtonDown -= ResizerTopLeftMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.ResizerTopLeft);
            }

            if (window.ResizerTopRight != null)
            {
                window.ResizerTopRight.MouseLeftButtonDown -= ResizerTopRightMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.ResizerTopRight);
            }

            if (window.ResizerBottomLeft != null)
            {
                window.ResizerBottomLeft.MouseLeftButtonDown -= ResizerBottomLeftMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.ResizerBottomLeft);
            }

            if (window.ResizerBottomRight != null)
            {
                window.ResizerBottomRight.MouseLeftButtonDown -= ResizerBottomRightMouseLeftButtonDown;
                ClickTargetLookup.Remove(window.ResizerBottomRight);
            }

        }

        /// <summary>
        /// Provided so that deriving classes can invoke the WindowDragStart event manually
        /// </summary>
        /// <param name="win"></param>
        protected void OnWindowDragStart(IWindow win)
        {
            OnWindowEvent(WindowDragStart, win);
        }

        #region protected properties

        /// <summary>
        /// Holds the z-order of the window
        /// </summary>
        protected int ZOrder { get; set; }

        /// <summary>
        /// Holds the current cursor type
        /// </summary>
        protected Cursor CurrentCursor { get; set; }

        #endregion
        
        #region LeftButtonDown Handlers
        
        private void WidthResizerRightMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeWE;
            CurrentTransformDelegate = TransformRight;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        private void HeightResizerBottomMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeNS;
            CurrentTransformDelegate = TransformDown;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        private void WidthResizerLeftMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeWE;
            CurrentTransformDelegate = TransformLeft;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        private void HeightResizerTopMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeNS;
            CurrentTransformDelegate = TransformUp;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        private void ResizerBottomRightMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeNWSE;
            CurrentTransformDelegate = TransformDownRight;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        private void ResizerBottomLeftMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeNESW;
            CurrentTransformDelegate = TransformDownLeft;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        private void ResizerTopRightMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeNESW;
            CurrentTransformDelegate = TransformUpRight;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        private void ResizerTopLeftMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CurrentCursor = Cursors.SizeNWSE;
            CurrentTransformDelegate = TransformUpLeft;
            FinalizeLeftMouseButtonDown(sender as FrameworkElement, e);
        }

        /// <summary>
        /// Fires on left mouse down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void DragMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var handle = sender as FrameworkElement;
            if (handle == default(FrameworkElement)) return;

            var win = ClickTargetLookup[handle] as IWindow;
            if (win == default(IWindow)) return;

            if (!win.DragEnabled) return;

            base.DragMouseLeftButtonDown(sender, e);
        }

        /// <summary>
        /// Called to finalize left button down activity
        /// </summary>
        /// <param name="element"></param>
        /// <param name="e"></param>
        protected override void FinalizeLeftMouseButtonDown(FrameworkElement element, MouseEventArgs e)
        {
            //e.Handled = true;
            var win = ClickTargetLookup[element] as IWindow;
            if (win == default(IWindow)) return;
    
            if (CurrentTransformDelegate != TransformDrag)
            {
                if (!win.ResizeEnabled) return;
                OnWindowEvent(WindowResizeStart, win);
                win.OnResizeStarted();
            }else
            {
                Canvas.SetZIndex(win.DragElement, ZOrder++);
                base.FinalizeLeftMouseButtonDown(element, e);
                OnWindowEvent(WindowDragStart, win);
            }

            RootPanel.Cursor = CurrentCursor;
            CapturedElement = element;

            ClickPoint = e.GetPosition(element);

            RootPanel.MouseLeftButtonUp += RootPanelMouseLeftButtonUp;
            RootPanel.MouseLeave += RootPanelMouseLeave;
        }

        #endregion

        #region MouseMove Handler
        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformUpLeft(FrameworkElement target, Point xy, TransformGroup g)
        {
            TransformLeft(target, xy, g);
            TransformUp(target, xy, g);
        }

        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformDownLeft(FrameworkElement target, Point xy, TransformGroup g)
        {
            TransformLeft(target, xy, g);
            TransformDown(target, xy, default(TransformGroup));
        }

        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformUpRight(FrameworkElement target, Point xy, TransformGroup g)
        {
            TransformRight(target, xy, default(TransformGroup));
            TransformUp(target, xy, g);
        }

        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformDownRight(FrameworkElement target, Point xy, TransformGroup g)
        {
            TransformRight(target, xy, default(TransformGroup));
            TransformDown(target, xy, default(TransformGroup));
        }

        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformRight(FrameworkElement target, Point xy, TransformGroup g)
        {
            target.Width = xy.X + 2 > 50 ? xy.X + 2 : 50;
        }

        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformDown(FrameworkElement target, Point xy, TransformGroup g)
        {
            target.Height = xy.Y + 2 > 50 ? xy.Y + 2 : 50;
        }

        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformLeft(FrameworkElement target, Point xy, TransformGroup g)
        {
            var t = new TranslateTransform { X = xy.X - ClickPoint.X };
            g.Children.Add(t);
            var newWidth = target.Width + -t.X > 50 ? target.Width + -t.X : 50;
            target.Width = newWidth;
            target.RenderTransform = g;
        }

        /// <summary>
        /// Transform Delegate
        /// </summary>
        /// <param name="target"></param>
        /// <param name="xy"></param>
        /// <param name="g"></param>
        protected void TransformUp(FrameworkElement target, Point xy, TransformGroup g)
        {
            if (target == null) throw new ArgumentNullException("target");
            if (g == null) throw new ArgumentNullException("g");

            var t = new TranslateTransform { Y = xy.Y - ClickPoint.Y };
            g.Children.Add(t);
            var newHeight = target.Height + -t.Y > 50 ? target.Height + -t.Y : 50;
            target.Height = newHeight;
            target.RenderTransform = g;
        }
        #endregion

        internal override void FinalizeMouseButtonUp()
        {
            // defer to base class if the mode is drag
            if (CurrentTransformDelegate == TransformDrag)
            {
                if (ClickTargetLookup.ContainsKey(CapturedElement))
                    OnWindowEvent(WindowDragComplete, (IWindow)ClickTargetLookup[CapturedElement]);
                base.FinalizeMouseButtonUp();
                return;
            }
            if (ClickTargetLookup.ContainsKey(CapturedElement))
                ((IWindow) ClickTargetLookup[CapturedElement]).OnResizeEnded();

            try
            {
                RootPanel.MouseLeftButtonUp -= RootPanelMouseLeftButtonUp;
            }
            // ReSharper disable EmptyGeneralCatchClause
            catch
            // ReSharper restore EmptyGeneralCatchClause
            { }

            try
            {
                RootPanel.MouseLeave -= RootPanelMouseLeave;
            }
            // ReSharper disable EmptyGeneralCatchClause
            catch
            // ReSharper restore EmptyGeneralCatchClause
            { }
            finally
            {
                if (ClickTargetLookup.ContainsKey(CapturedElement))
                    OnWindowEvent(WindowResizeComplete, (IWindow)ClickTargetLookup[CapturedElement]);
                CurrentTransformDelegate = null;
                RootPanel.Cursor = Cursors.Arrow;
                CurrentCursor = Cursors.Arrow;
            }
        }

        /// <summary>
        /// Returns a lazy list of IWindow objects which are registered to the service, at the time the lazy object is instantiated.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<IWindow> GetRegisteredWindows()
        {
            return ClickTargetLookup
                .Values
                .Distinct()
                .OfType<IWindow>()
                .Select(o => o);
        }

        /// <summary>
        /// Gets the IWindow object that was most recently part of a drag operation
        /// </summary>
        public IWindow LastDraggedWindow
        {
            get
            { return ClickTargetLookup[CapturedElement] as IWindow; }
        }

        /// <summary>
        /// Fires when a user begins a drag operation on a IWindow object.
        /// </summary>
        public event EventHandler<WindowManipulationEventArgs> WindowDragStart;
        /// <summary>
        /// Fires when a user completes a drag operation on a IWindow object.
        /// </summary>
        public event EventHandler<WindowManipulationEventArgs> WindowDragComplete;
        /// <summary>
        /// Fires when a user begins a resize operation on a IWindow object..
        /// </summary>
        public event EventHandler<WindowManipulationEventArgs> WindowResizeStart;
        /// <summary>
        /// Fires when a user begins a drag operation on a IWindow object.
        /// </summary>
        public event EventHandler<WindowManipulationEventArgs> WindowResizeComplete;

    }
}
